#  Copyright 2008-2013 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from robot.running.defaults import TestDefaults
from robot.parsing import TestData
from robot.errors import DataError
from robot.utils import abspath

from .model import TestSuite, ForLoop


class TestSuiteBuilder(object):

    def __init__(self, include_suites=None, warn_on_skipped=False, include_empty_suites=False):
        """Create programmatically executable
        :class:`~robot.running.model.TestSuite` objects based on existing data
        on the file system.

        See example of usage in :mod:`.running` package.
        """
        self.include_suites = include_suites
        self.warn_on_skipped = warn_on_skipped
        self.include_empty_suites = include_empty_suites

    def build(self, *paths):
        if not paths:
            raise DataError('One or more source paths required.')
        if len(paths) == 1:
            return self._build_and_check_if_empty(paths[0])
        root = TestSuite()
        for path in paths:
            root.suites.append(self._build_and_check_if_empty(path))
        return root

    def _build_and_check_if_empty(self, path):
        builded = self._build_suite(self._parse(path))
        if not self._empty_suites_allowed and not builded.test_count:
                raise DataError("Suite '%s' contains no tests." % builded.name)
        builded.remove_empty_suites()
        return builded

    @property
    def _empty_suites_allowed(self):
        return self.include_empty_suites or self.include_suites


    def _parse(self, path):
        try:
            return TestData(source=abspath(path),
                            include_suites=self.include_suites,
                            warn_on_skipped=self.warn_on_skipped)
        except DataError, err:
            raise DataError("Parsing '%s' failed: %s" % (path, unicode(err)))

    def _build_suite(self, data, parent_defaults=None):
        defaults = TestDefaults(data.setting_table, parent_defaults)
        suite = TestSuite(name=data.name,
                          source=data.source,
                          doc=unicode(data.setting_table.doc),
                          metadata=self._get_metadata(data.setting_table))
        for import_data in data.setting_table.imports:
            self._create_import(suite, import_data)
        self._create_setup(suite, data.setting_table.suite_setup)
        self._create_teardown(suite, data.setting_table.suite_teardown)
        for var_data in data.variable_table.variables:
            self._create_variable(suite, var_data)
        for uk_data in data.keyword_table.keywords:
            self._create_user_keyword(suite, uk_data)
        for test_data in data.testcase_table.tests:
            self._create_test(suite, test_data, defaults)
        for child in data.children:
            suite.suites.append(self._build_suite(child, defaults))
        return suite

    def _get_metadata(self, settings):
        # Must return as a list to preserve ordering
        return [(meta.name, meta.value) for meta in settings.metadata]

    def _create_import(self, suite, data):
        suite.imports.create(type=data.type,
                             name=data.name,
                             args=tuple(data.args),
                             alias=data.alias)

    def _create_test(self, suite, data, defaults):
        values = defaults.get_test_values(data)
        test = suite.tests.create(name=data.name,
                                  doc=unicode(data.doc),
                                  tags=values.tags.value,
                                  template=self._get_template(values.template),
                                  timeout=self._get_timeout(values.timeout))
        self._create_setup(test, values.setup)
        for step_data in data.steps:
            self._create_step(test, step_data, template=values.template)
        self._create_teardown(test, values.teardown)

    def _get_timeout(self, timeout):
        return (timeout.value, timeout.message) if timeout else None

    def _get_template(self, template):
        return unicode(template) if template.is_active() else None

    def _create_user_keyword(self, suite, data):
        uk = suite.user_keywords.create(name=data.name,
                                        args=tuple(data.args),
                                        doc=unicode(data.doc),
                                        return_=tuple(data.return_),
                                        timeout=data.timeout,
                                        teardown=data.teardown)
        for step_data in data.steps:
            self._create_step(uk, step_data)

    def _create_variable(self, suite, data):
        if data:
            suite.variables.create(name=data.name, value=data.value)

    def _create_setup(self, parent, data):
        if data.is_active():
            self._create_step(parent, data, kw_type='setup')

    def _create_teardown(self, parent, data):
        if data.is_active():
            self._create_step(parent, data, kw_type='teardown')

    def _create_step(self, parent, data, template=None, kw_type='kw'):
        if not data or data.is_comment():
            return
        if data.is_for_loop():
            self._create_for_loop(parent, data, template)
        elif template and template.is_active():
            parent.keywords.create(name=unicode(template),
                                   args=tuple(data.as_list(include_comment=False)))
        else:
            parent.keywords.create(name=data.keyword,
                                   args=tuple(data.args),
                                   assign=tuple(data.assign),
                                   type=kw_type)

    def _create_for_loop(self, parent, data, template):
        loop = parent.keywords.append(ForLoop(vars=data.vars,
                                              items=data.items,
                                              range=data.range))
        for step in data.steps:
            self._create_step(loop, step, template=template)
